package com.zzg.mybatis.generator.plugins;

import com.zzg.mybatis.generator.model.UITableQueryColumnVO;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.Plugin;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.*;
import org.mybatis.generator.codegen.AbstractJavaGenerator;
import org.mybatis.generator.codegen.RootClassInfo;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

import static org.mybatis.generator.internal.util.JavaBeansUtil.*;
import static org.mybatis.generator.internal.util.messages.Messages.getString;

/**
 * @author 石超
 * @version v1.0.0
 */
public class QueryGenerator extends AbstractJavaGenerator {

    private static final List<String> INSERT_LIST = Arrays.asList("create_by", "create_time");
    private static final List<String> UPDATE_LIST = Arrays.asList("update_by", "update_time");
    private static final String DEL_FLAG = "del_flag";

    private boolean isQuery = false;

    private List<UITableQueryColumnVO> queryColumnVOS;

    private String clientPackage;
    private String modelPackage;
    private String servicePackage;
    private String controllerPackage;
    private boolean isMybatisPlus = false;
    private boolean hasDelFlag = false;
    private boolean hasMybatisPlusList = false;
    private Map<String, List<String>> query = new HashMap<>();

    private Document document;

    public QueryGenerator() {
        super();
    }

    @Override
    public List<CompilationUnit> getCompilationUnits() {
        List<CompilationUnit> answer = new ArrayList<>();

        addTopLevelClass(getModel("DO"), answer);
        addTopLevelClass(getModel("DTO"), answer);

        if (isQuery = CollectionUtils.isNotEmpty(queryColumnVOS) || isMybatisPlus) {
            if (queryColumnVOS == null) queryColumnVOS = Collections.emptyList();
//            addTopLevelClass((getQueryModel("QueryDO")), answer);
            addTopLevelClass(getQuerySql(getQueryModel("QueryDTO")), answer);
        }

        getService(answer);

        return answer;
    }

    private void getService(List<CompilationUnit> answer) {
        CommentGenerator commentGenerator = context.getCommentGenerator();
        Interface clientInterface;
        TopLevelClass serviceTopLevelClass;
        TopLevelClass controllerTopLevelClass;

        if (StringUtils.isNotBlank(clientPackage)) {
            clientInterface = new Interface(clientPackage + "." + introspectedTable.getTableConfiguration().getDomainObjectName() + "Service");
            clientInterface.addImportedType(new FullyQualifiedJavaType(get("DTO")));

            if (isQuery) {
                if (isMybatisPlus) {
                    clientInterface.addImportedType(new FullyQualifiedJavaType(get("DO")));
                    clientInterface.addImportedType(new FullyQualifiedJavaType(get("QueryDTO")));
                    clientInterface.addImportedType(new FullyQualifiedJavaType("org.summerframework.component.mybatisplus.BasePlusQueryService"));
                    clientInterface.addSuperInterface(new FullyQualifiedJavaType("org.summerframework.component.mybatisplus.BasePlusQueryService<" + get("DTO") + ", " + get("QueryDTO") + ">"));
                } else {
                    clientInterface.addImportedType(new FullyQualifiedJavaType(get("QueryDTO")));
                    clientInterface.addImportedType(new FullyQualifiedJavaType("org.summerframework.core.base.service.BaseQueryService"));
                    clientInterface.addSuperInterface(new FullyQualifiedJavaType("org.summerframework.core.base.service.BaseQueryService<" + get("DTO") + ", " + get("QueryDTO") + ">"));

                }
            } else {
                clientInterface.addImportedType(new FullyQualifiedJavaType("org.summerframework.core.base.service.BaseService"));
                clientInterface.addSuperInterface(new FullyQualifiedJavaType("org.summerframework.core.base.service.BaseService<" + get("DTO") + ">"));
            }

            clientInterface.setVisibility(JavaVisibility.PUBLIC);
            commentGenerator.addJavaFileComment(clientInterface);
            answer.add(clientInterface);
        } else {
            return;
        }


        if (StringUtils.isNotBlank(servicePackage)) {
            serviceTopLevelClass = new TopLevelClass(servicePackage + "." + introspectedTable.getTableConfiguration().getDomainObjectName() + "ServiceImpl");
            serviceTopLevelClass.addImportedType(get("DTO"));
            FullyQualifiedJavaType javaMapper = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType());
            String javaMapperName = firstCharacterUppercase(javaMapper.getShortName(), false);
            serviceTopLevelClass.addImportedType(javaMapper);
            serviceTopLevelClass.addImportedType(clientInterface.getType());
            serviceTopLevelClass.addSuperInterface(clientInterface.getType());

            if (isQuery) {
                if (isMybatisPlus) {
                    serviceTopLevelClass.addImportedType(get("QueryDTO"));
                    serviceTopLevelClass.addImportedType("org.summerframework.component.mybatisplus.AbstractBasePlusService");
                    serviceTopLevelClass.addImportedType(get("DO"));
                    serviceTopLevelClass.addImportedType("com.baomidou.mybatisplus.core.toolkit.Wrappers");
                    serviceTopLevelClass.addImportedType("java.util.Objects");
                    serviceTopLevelClass.addImportedType("org.apache.commons.lang3.StringUtils");
                    serviceTopLevelClass.setSuperClass(String.format("org.summerframework.component.mybatisplus.AbstractBasePlusService<%s, %s ,%s ,%s>", javaMapper.getShortName(), getName("DTO"), get("QueryDTO"), get("DO")));

                    serviceTopLevelClass.addImportedType("com.baomidou.mybatisplus.core.conditions.Wrapper");

                    Method getWrapper = new Method();
                    getWrapper.setVisibility(JavaVisibility.PUBLIC);
                    getWrapper.addAnnotation("@Override");
                    getWrapper.setName("getWrapper");
                    getWrapper.addParameter(new Parameter(new FullyQualifiedJavaType(get("QueryDTO")), "query"));
                    getWrapper.setReturnType(new FullyQualifiedJavaType("Wrapper<" + get("DO") + ">"));
                    StringBuilder sb = new StringBuilder();
                    sb.append("Wrappers.<").append(getName("DO")).append(">lambdaQuery()");
                    this.query.values().forEach(list -> {
                        list.forEach(sb::append);
                    });
                    getWrapper.addBodyLine("return " + sb.toString() + ";");

                    serviceTopLevelClass.addMethod(getWrapper);
                } else {
                    serviceTopLevelClass.addImportedType(get("QueryDTO"));
                    serviceTopLevelClass.addImportedType("org.summerframework.core.base.service.impl.AbstractBaseQueryService");
                    serviceTopLevelClass.setSuperClass(String.format("org.summerframework.core.base.service.impl.AbstractBaseQueryService<%s, %s, %s, %s>", javaMapper.getShortName(), getName("DTO"), get("QueryDTO"), get("DO")));
                }
            } else {
                serviceTopLevelClass.addImportedType("org.summerframework.core.base.service.impl.AbstractBaseService");
                serviceTopLevelClass.setSuperClass("org.summerframework.core.base.service.impl.AbstractBaseService<" + get("DTO") + ">");
            }

            serviceTopLevelClass.addImportedType("org.springframework.stereotype.Service");
            serviceTopLevelClass.addAnnotation("@Service(\"" + firstCharacterUppercase(clientInterface.getType().getShortName(), false) + "\")");
            serviceTopLevelClass.setVisibility(JavaVisibility.PUBLIC);
            commentGenerator.addJavaFileComment(serviceTopLevelClass);

            answer.add(serviceTopLevelClass);
        } else {
            return;
        }

        if (StringUtils.isNotBlank(controllerPackage)) {
            FullyQualifiedJavaType javaMapper = clientInterface.getType();
            String javaMapperName = firstCharacterUppercase(javaMapper.getShortName(), false);

            controllerTopLevelClass = new TopLevelClass(controllerPackage + "." + introspectedTable.getTableConfiguration().getDomainObjectName() + "Controller");
            controllerTopLevelClass.addImportedType(get("DTO"));
            controllerTopLevelClass.setSuperClass("org.summerframework.component.security.controller.BaseController");
            controllerTopLevelClass.addImportedType("org.summerframework.component.security.controller.BaseController");
            controllerTopLevelClass.addImportedType("org.summerframework.core.exception.SelectNullException");
            controllerTopLevelClass.addImportedType("org.springframework.web.bind.annotation.RequestParam");
            controllerTopLevelClass.addImportedType("java.util.List");
            controllerTopLevelClass.addImportedType("org.summerframework.core.base.result.ResultDTO");
            controllerTopLevelClass.addImportedType("javax.annotation.Resource");
            controllerTopLevelClass.addImportedType("org.springframework.web.bind.annotation.*");
            controllerTopLevelClass.addImportedType("io.swagger.annotations.ApiImplicitParams");
            controllerTopLevelClass.addImportedType("io.swagger.annotations.ApiImplicitParam");
            controllerTopLevelClass.addImportedType("io.swagger.annotations.ApiOperation");
            controllerTopLevelClass.addImportedType("io.swagger.annotations.Api");
            controllerTopLevelClass.addImportedType("org.summerframework.core.util.OptionalUtils");

            controllerTopLevelClass.addAnnotation("@RestController");
            controllerTopLevelClass.addAnnotation("@Api(tags = \"功能名称\", description= \"功能描述\")");
            controllerTopLevelClass.addAnnotation("@RequestMapping(\"" + bigLetterToSmallAndBeforeAdd_(firstCharacterUppercase(introspectedTable.getTableConfiguration().getDomainObjectName(), false)) + "\")");
            controllerTopLevelClass.setVisibility(JavaVisibility.PUBLIC);
            commentGenerator.addJavaFileComment(controllerTopLevelClass);
            controllerTopLevelClass.addImportedType(javaMapper);

            Field field = new Field();
            field.setVisibility(JavaVisibility.PRIVATE);
            field.setType(javaMapper);
            field.setName(javaMapperName);
            field.addAnnotation("@Resource");
            controllerTopLevelClass.addField(field);
            answer.add(controllerTopLevelClass);


            FullyQualifiedJavaType dto = new FullyQualifiedJavaType(get("DTO"));

            Parameter parameter = new Parameter(new FullyQualifiedJavaType("java.util.List<java.lang.Long>"), "ids");
            parameter.addAnnotation("@RequestParam(\"ids\")");

            Method addMethod = addMethod("add", "insert", javaMapperName, dto, "ResultDTO.ok(true, %s.%s(%s));", "@PostMapping", "org.summerframework.core.base.result.ResultDTO<java.lang.Boolean>");
            Method updateMethod = addMethod("update", "updateById", javaMapperName, dto, null, "@PutMapping", "org.summerframework.core.base.result.ResultDTO<java.lang.Integer>");
            Method deleteMethod = addMethod("delete", "deleteById", javaMapperName, null, "@DeleteMapping(\"{id}\")", "org.summerframework.core.base.result.ResultDTO<java.lang.Integer>");
            Method deletesMethod = addMethod("deleteByIds", "deleteByIds", javaMapperName, parameter, null, "@DeleteMapping", "org.summerframework.core.base.result.ResultDTO<java.lang.Integer>");
            Method selectMethod = addMethod("select", "selectById", javaMapperName, "ResultDTO.ok(OptionalUtils.get(%s.%s(%s)));", "@GetMapping(\"{id}\")", "org.summerframework.core.base.result.ResultDTO<" + get("DTO") + ">");

            List<IntrospectedColumn> idIntrospectedColumns = getIntrospectedTable().getPrimaryKeyColumns();
            List<IntrospectedColumn> notIdIntrospectedColumns = getIntrospectedTable().getAllColumns();

            addMethod.addAnnotation(swaggerApiOperation("添加" + getIntrospectedTable().getRemarks()));
            updateMethod.addAnnotation(swaggerApiOperation("更新" + getIntrospectedTable().getRemarks()));
            deleteMethod.addAnnotation(swaggerApiOperation("根据id删除" + getIntrospectedTable().getRemarks()));
            deletesMethod.addAnnotation(swaggerApiOperation("根据id列表删除删除" + getIntrospectedTable().getRemarks()));
            selectMethod.addAnnotation(swaggerApiOperation("根据id查询" + getIntrospectedTable().getRemarks()));
//            addMethod.addAnnotation(swagger(notIdIntrospectedColumns));
            IntrospectedColumn introspectedColumn = idIntrospectedColumns.get(0);
            String updateAnnotation = swaggers(introspectedColumn.getJavaProperty(), introspectedColumn.getRemarks(), true, introspectedColumn.getFullyQualifiedJavaType().getShortName(), "query");
            String deletesAnnotation = swaggers("ids", "根据ids删除", true, "List", "query");
            String idAnnotation = swaggers(introspectedColumn.getJavaProperty(), introspectedColumn.getRemarks(), true, introspectedColumn.getFullyQualifiedJavaType().getShortName(), "path");
            updateMethod.addAnnotation(updateAnnotation);
            deleteMethod.addAnnotation(idAnnotation);
            deletesMethod.addAnnotation(deletesAnnotation);
            selectMethod.addAnnotation(idAnnotation);

            controllerTopLevelClass.addMethod(addMethod);
            controllerTopLevelClass.addMethod(updateMethod);
            controllerTopLevelClass.addMethod(deleteMethod);
            controllerTopLevelClass.addMethod(deletesMethod);
            controllerTopLevelClass.addMethod(selectMethod);

            if (isQuery) {
                controllerTopLevelClass.addImportedType("org.summerframework.core.page.PageResult");
                FullyQualifiedJavaType queryDTO = new FullyQualifiedJavaType(get("QueryDTO"));
                controllerTopLevelClass.addImportedType(queryDTO);
                Method selectQueryMethod = addMethod("list", "selectPageResultByQuery", javaMapperName, queryDTO, "%s.%s(%s);", "@GetMapping", "org.summerframework.core.page.PageResult<" + get("DTO") + ">");
                selectQueryMethod.addAnnotation(swaggerApiOperation("根据query查询" + getIntrospectedTable().getRemarks()));
//                selectQueryMethod.addAnnotation(swaggerQuery(queryColumnVOS));
                controllerTopLevelClass.addMethod(selectQueryMethod);
            }
        }
    }

    private String swaggerQuery(List<UITableQueryColumnVO> uiTableQueryColumnVOS) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("@ApiImplicitParams({");
        List<String> list = new ArrayList<>();

        for (UITableQueryColumnVO uiTableQueryColumnVO : uiTableQueryColumnVOS) {
            for (String key : uiTableQueryColumnVO.getFieldMap().keySet()) {
                Field field = uiTableQueryColumnVO.getFieldMap().get(key);
                list.add(swagger(field.getName(), key, false, field.getType().getFullyQualifiedName(), "query"));
            }
        }

        list.add(swagger("pageIndex", "当前页码", false, "Integer", "query"));
        list.add(swagger("pageSize", "分页数量", false, "Integer", "query"));
        list.add(swagger("isPagination", "是否分页, false查询所有数据", false, "Boolean", "query"));
        stringBuffer.append(StringUtils.join(list, ","));
        stringBuffer.append("})");

        return stringBuffer.toString();
    }

    private String swaggerApiOperation(String value) {
        return String.format("@ApiOperation(\"%s\")", value);
    }

    private String swagger(String name, String value, boolean required, String dataType, String paramType) {
        return String.format("\n@ApiImplicitParam(name = \"%s\", value = \"%s\", required = %b, dataType = \"%s\", paramType = \"%s\")", name, makeQueryStringAllRegExp(value), required, dataType, paramType);
    }

    private String swaggers(String name, String value, boolean required, String dataType, String paramType) {
        return String.format("@ApiImplicitParams({\n@ApiImplicitParam(name = \"%s\", value = \"%s\", required = %b, dataType = \"%s\", paramType = \"%s\")})", name, makeQueryStringAllRegExp(value), required, dataType, paramType);
    }

    private String swagger(List<IntrospectedColumn> introspectedColumns) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("@ApiImplicitParams({");
        List<String> list = new ArrayList<>();

        for (IntrospectedColumn introspectedColumn : introspectedColumns) {
            list.add(swagger(introspectedColumn.getJavaProperty(), introspectedColumn.getRemarks(), !introspectedColumn.isNullable(), introspectedColumn.getFullyQualifiedJavaType().getShortName(), "query"));
        }
        stringBuffer.append(StringUtils.join(list, ",\n"));
        stringBuffer.append("\n})");

        return stringBuffer.toString();
    }

    public static String makeQueryStringAllRegExp(String str) {
        if (StringUtils.isBlank(str)) {
            return str;
        }

        return str.replace("\"", "");
    }

    private Method addMethod(String name, String methodName, String service, String bodyLine, String annotation, String returnType) {
        Parameter parameter = new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id");
        parameter.addAnnotation("@PathVariable(\"id\")");
        return addMethod(name, methodName, service, parameter, bodyLine, annotation, returnType);
    }


    private Method addMethod(String name, String methodName, String service, FullyQualifiedJavaType fullyQualifiedJavaType, String bodyLine, String annotation, String returnType) {
        return addMethod(name, methodName, service, new Parameter(fullyQualifiedJavaType, firstCharacterUppercase(fullyQualifiedJavaType.getShortName(), false)), bodyLine, annotation, returnType);
    }

    private Method addMethod(String name, String methodName, String service, Parameter parameter, String bodyLine, String annotation, String returnType) {
        Method method = new Method();
        method.addAnnotation(annotation);
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addParameter(parameter);
        method.setName(methodName);
        method.setReturnType(new FullyQualifiedJavaType(returnType));
        method.addBodyLine("return " + (String.format(StringUtils.isNotBlank(bodyLine) ? bodyLine : "ResultDTO.ok(%s.%s(%s));", service, methodName, parameter.getName())));

        return method;
    }

    private String get(String end) {
        return (end.endsWith("DO") ? modelPackage + ".domain." : clientPackage + ".dto.") + introspectedTable.getTableConfiguration().getDomainObjectName() + end;
    }

    private String getName(String end) {
        return introspectedTable.getTableConfiguration().getDomainObjectName() + end;
    }

    private TopLevelClass getModel(String end) {
        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        progressCallback.startTask(getString("Progress.8", table.toString())); //$NON-NLS-1$
        Plugin plugins = context.getPlugins();
        CommentGenerator commentGenerator = context.getCommentGenerator();

        TopLevelClass topLevelClass = new TopLevelClass(get(end));
        topLevelClass.setVisibility(JavaVisibility.PUBLIC);
        commentGenerator.addJavaFileComment(topLevelClass);
        FullyQualifiedJavaType superClass = getSuperClass();
        boolean flag = false;

        if (end.endsWith("DO")) {
            topLevelClass.addImportedType("org.summerframework.core.base.dto.BaseDO");
            topLevelClass.setSuperClass("org.summerframework.core.base.dto.BaseDO");
            topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.TableName");
            topLevelClass.addAnnotation("@TableName(\"" + introspectedTable.getTableConfiguration().getTableName() + "\")");
        } else if (end.endsWith("DTO")) {
            topLevelClass.addImportedType("lombok.experimental.SuperBuilder");
            topLevelClass.addImportedType("lombok.NoArgsConstructor");
            topLevelClass.addImportedType("lombok.AllArgsConstructor");
            topLevelClass.addAnnotation("@SuperBuilder");
            topLevelClass.addAnnotation("@NoArgsConstructor");
            topLevelClass.addAnnotation("@AllArgsConstructor");

            topLevelClass.addImportedType("org.summerframework.core.base.dto.BaseDTO");
            topLevelClass.setSuperClass("org.summerframework.core.base.dto.BaseDTO");
            topLevelClass.addImportedType("io.swagger.annotations.ApiModelProperty");
            flag = true;
        }

        if (superClass != null) {
            topLevelClass.setSuperClass(superClass);
            topLevelClass.addImportedType(superClass);
        }

        commentGenerator.addModelClassComment(topLevelClass, introspectedTable);

        List<IntrospectedColumn> introspectedColumns = getColumnsInThisClass();
        introspectedColumns.stream().map(IntrospectedColumn::getJavaProperty).collect(Collectors.toList());

        if (introspectedTable.isConstructorBased()) {
            addParameterizedConstructor(topLevelClass, introspectedTable.getNonBLOBColumns());

            if (includeBLOBColumns()) {
                addParameterizedConstructor(topLevelClass, introspectedTable.getAllColumns());
            }

            if (!introspectedTable.isImmutable()) {
                addDefaultConstructor(topLevelClass);
            }
        }

        String rootClass = getRootClass();
        for (IntrospectedColumn introspectedColumn : introspectedColumns) {
            if (RootClassInfo.getInstance(rootClass, warnings).containsProperty(introspectedColumn) || introspectedColumn.getJavaProperty().equals("id")) {
                continue;
            }

            Field field = getJavaBeansField(introspectedColumn, context, introspectedTable);
            if (plugins.modelFieldGenerated(field, topLevelClass,
                    introspectedColumn, introspectedTable,
                    Plugin.ModelClassType.BASE_RECORD)) {
                topLevelClass.addField(field);
                topLevelClass.addImportedType(field.getType());
                addApiModelProperty(flag, topLevelClass, field, introspectedColumn.getRemarks(), !introspectedColumn.isNullable() && introspectedColumn.getDefaultValue() == null);

                if (isMybatisPlus && !flag) {
                    if (introspectedColumn.getActualColumnName().equals(DEL_FLAG)) {
                        topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.TableLogic");
                        field.addAnnotation("@TableLogic");
                    } else if (INSERT_LIST.contains(introspectedColumn.getActualColumnName())) {
                        topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.TableField");
                        topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.FieldFill");

                        field.addAnnotation("@TableField(fill = FieldFill.INSERT)");
                    } else if (UPDATE_LIST.contains(introspectedColumn.getActualColumnName())) {
                        topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.TableField");
                        topLevelClass.addImportedType("com.baomidou.mybatisplus.annotation.FieldFill");

                        field.addAnnotation("@TableField(fill = FieldFill.INSERT_UPDATE)");
                    }
                }
            }

            Method method = getJavaBeansGetter(introspectedColumn, context, introspectedTable);
            if (plugins.modelGetterMethodGenerated(method, topLevelClass,
                    introspectedColumn, introspectedTable,
                    Plugin.ModelClassType.BASE_RECORD)) {
                topLevelClass.addMethod(method);
            }

            if (!introspectedTable.isImmutable()) {
                method = getJavaBeansSetter(introspectedColumn, context, introspectedTable);
                if (plugins.modelSetterMethodGenerated(method, topLevelClass,
                        introspectedColumn, introspectedTable,
                        Plugin.ModelClassType.BASE_RECORD)) {
                    topLevelClass.addMethod(method);
                }
            }
        }

        return topLevelClass;
    }

    private TopLevelClass getQuerySql(TopLevelClass topLevelClass) {
        if (isMybatisPlus) return topLevelClass;
        String DO = topLevelClass.getType().getFullyQualifiedName();
        XmlElement element = document.getRootElement();
        XmlElement sqlXmlElement = new XmlElement("sql");
        sqlXmlElement.addAttribute(new Attribute("id", "query_condition"));
        XmlElement sqlTrimXmlElement = new XmlElement("trim");
        sqlTrimXmlElement.addAttribute(new Attribute("prefix", "where"));
        sqlTrimXmlElement.addAttribute(new Attribute("prefixOverrides", "and |or"));
        sqlXmlElement.addElement(sqlTrimXmlElement);

        XmlElement orderXmlElement = new XmlElement("sql");
        orderXmlElement.addAttribute(new Attribute("id", "order_condition"));
        XmlElement orderTrimXmlElement = new XmlElement("trim");
        orderTrimXmlElement.addAttribute(new Attribute("prefix", "order by"));
        orderTrimXmlElement.addAttribute(new Attribute("prefixOverrides", ","));
        orderXmlElement.addElement(orderTrimXmlElement);

        XmlElement limitXmlElement = new XmlElement("sql");
        limitXmlElement.addAttribute(new Attribute("id", "limit_condition"));
        XmlElement isPagination = new XmlElement("if");
        isPagination.addAttribute(new Attribute("test", "isPagination"));
        isPagination.addElement(new TextElement("limit #{startRow,jdbcType=INTEGER}, #{pageSize,jdbcType=INTEGER}"));
        limitXmlElement.addElement(isPagination);

        for (UITableQueryColumnVO uiTableQueryColumnVO : queryColumnVOS) {
            IntrospectedColumn introspectedColumn = introspectedTable.getColumn(uiTableQueryColumnVO.getColumnName());

            if (introspectedColumn == null) {
                continue;
            }

            if (uiTableQueryColumnVO.isRuzzyQuery()) {
                IntrospectedColumn searchIntrospectedColumn = new IntrospectedColumn();
                try {
                    BeanUtils.copyProperties(searchIntrospectedColumn, introspectedColumn);

                    searchIntrospectedColumn.setJavaProperty("search" + firstCharacterUppercase(searchIntrospectedColumn.getJavaProperty(), true));
                    addSqlIf(searchIntrospectedColumn, sqlTrimXmlElement, "%s != null and %s != ''", String.format("and %s like concat('%%',#{%s, jdbcType=%s},'%%'))", searchIntrospectedColumn.getActualColumnName(), searchIntrospectedColumn.getJavaProperty(), searchIntrospectedColumn.getJdbcTypeName()));
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            if (uiTableQueryColumnVO.isRangeQuery()) {
                IntrospectedColumn startIntrospectedColumn = new IntrospectedColumn();
                IntrospectedColumn endIntrospectedColumn = new IntrospectedColumn();

                try {
                    BeanUtils.copyProperties(startIntrospectedColumn, introspectedColumn);
                    BeanUtils.copyProperties(endIntrospectedColumn, introspectedColumn);

                    startIntrospectedColumn.setJavaProperty("start" + firstCharacterUppercase(startIntrospectedColumn.getJavaProperty(), true));
                    endIntrospectedColumn.setJavaProperty("end" + firstCharacterUppercase(endIntrospectedColumn.getJavaProperty(), true));
                    addSqlIf(startIntrospectedColumn, sqlTrimXmlElement, String.format("and %s &gt; #{%s, jdbcType=%s}", startIntrospectedColumn.getActualColumnName(), startIntrospectedColumn.getJavaProperty(), startIntrospectedColumn.getJdbcTypeName()));
                    addSqlIf(endIntrospectedColumn, sqlTrimXmlElement, String.format("and %s &lt; #{%s, jdbcType=%s}", endIntrospectedColumn.getActualColumnName(), endIntrospectedColumn.getJavaProperty(), endIntrospectedColumn.getJdbcTypeName()));
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            if (uiTableQueryColumnVO.isOrderBy()) {
                IntrospectedColumn orderByIntrospectedColumn = new IntrospectedColumn();

                try {
                    BeanUtils.copyProperties(orderByIntrospectedColumn, introspectedColumn);

                    orderByIntrospectedColumn.setJavaProperty("orderBy" + firstCharacterUppercase(orderByIntrospectedColumn.getJavaProperty(), true));

                    XmlElement xmlElement = new XmlElement("if");
                    xmlElement.addAttribute(new Attribute("test", String.format("%s != null", orderByIntrospectedColumn.getJavaProperty())));

                    xmlElement.addElement(new TextElement(String.format(",%s", orderByIntrospectedColumn.getActualColumnName())));
                    XmlElement choose = new XmlElement("choose");
                    XmlElement whenTrue = new XmlElement("when");
                    whenTrue.addAttribute(new Attribute("test", orderByIntrospectedColumn.getJavaProperty()));
                    whenTrue.addElement(new TextElement("asc"));
                    choose.addElement(whenTrue);
                    XmlElement otherwise = new XmlElement("otherwise");
                    otherwise.addElement(new TextElement("desc"));
                    choose.addElement(otherwise);
                    xmlElement.addElement(choose);
                    orderTrimXmlElement.addElement(xmlElement);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            if (uiTableQueryColumnVO.isChecked()) {
                addSqlIf(introspectedColumn, sqlTrimXmlElement, String.format("and %s = #{%s, jdbcType=%s}", introspectedColumn.getActualColumnName(), introspectedColumn.getJavaProperty(), introspectedColumn.getJdbcTypeName()));
            }
        }

        element.addElement(sqlXmlElement);
        element.addElement(limitXmlElement);


        XmlElement selectByQuery = new XmlElement("select");
        selectByQuery.addAttribute(new Attribute("id", "selectByQuery"));
        selectByQuery.addAttribute(new Attribute("resultMap", introspectedTable.getRules().generateResultMapWithBLOBs() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId()));
        selectByQuery.addAttribute(new Attribute("parameterType", DO));
        selectByQuery.addElement(new TextElement("select"));
        selectByQuery.addElement(include(introspectedTable.getBaseColumnListId()));

        if (introspectedTable.getRules().generateResultMapWithBLOBs()) {
            selectByQuery.addElement(new TextElement(","));
            selectByQuery.addElement(include(introspectedTable.getBlobColumnListId()));
        }

        selectByQuery.addElement(new TextElement(String.format("from %s", introspectedTable.getTableConfiguration().getTableName())));
        selectByQuery.addElement(include("query_condition"));
        element.addElement(selectByQuery);


        XmlElement selectselectCountByQueryByQuery = new XmlElement("select");
        selectselectCountByQueryByQuery.addAttribute(new Attribute("id", "selectCountByQuery"));
        selectselectCountByQueryByQuery.addAttribute(new Attribute("resultType", "java.lang.Integer"));
        selectselectCountByQueryByQuery.addAttribute(new Attribute("parameterType", DO));
        selectselectCountByQueryByQuery.addElement(new TextElement("select"));
        selectselectCountByQueryByQuery.addElement(new TextElement("count(1)"));
        selectselectCountByQueryByQuery.addElement(new TextElement(String.format("from %s", introspectedTable.getTableConfiguration().getTableName())));

        if (!sqlTrimXmlElement.getElements().isEmpty()) {
            selectselectCountByQueryByQuery.addElement(include("query_condition"));
        }
        element.addElement(selectselectCountByQueryByQuery);
        element.addElement(orderXmlElement);
        selectByQuery.addElement(include("order_condition"));

        selectByQuery.addElement(include("limit_condition"));


        return topLevelClass;
    }

    private String firstCharacterUppercase(String str, boolean firstCharacterUppercase) {
        return (firstCharacterUppercase ? str.substring(0, 1).toUpperCase() : str.substring(0, 1).toLowerCase()) + str.substring(1);
    }

    private TopLevelClass getQueryModel(String end) {
        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        progressCallback.startTask(getString("Progress.8", table.toString())); //$NON-NLS-1$
        CommentGenerator commentGenerator = context.getCommentGenerator();
        TopLevelClass topLevelClass = new TopLevelClass(get(end));
        topLevelClass.setVisibility(JavaVisibility.PUBLIC);
        commentGenerator.addJavaFileComment(topLevelClass);
        boolean flag = false;

        topLevelClass.addImportedType("lombok.experimental.SuperBuilder");
        topLevelClass.addImportedType("lombok.NoArgsConstructor");
        topLevelClass.addImportedType("lombok.AllArgsConstructor");
        topLevelClass.addAnnotation("@SuperBuilder");
        topLevelClass.addAnnotation("@NoArgsConstructor");
        topLevelClass.addAnnotation("@AllArgsConstructor");

        if (end.endsWith("DO")) {
            topLevelClass.addImportedType("org.summerframework.core.base.dto.BaseQueryDO");
            topLevelClass.setSuperClass("org.summerframework.core.base.dto.BaseQueryDO");
        } else if (end.endsWith("DTO")) {
            topLevelClass.addImportedType("org.summerframework.core.page.PaginationQuery");
            topLevelClass.setSuperClass("org.summerframework.core.page.PaginationQuery");
            topLevelClass.addImportedType("io.swagger.annotations.ApiModelProperty");
            flag = true;
        }

        FullyQualifiedJavaType superClass = getSuperClass();

        if (superClass != null) {
            topLevelClass.setSuperClass(superClass);
            topLevelClass.addImportedType(superClass);
        }
        commentGenerator.addModelClassComment(topLevelClass, introspectedTable);

        String rootClass = getRootClass();

        for (UITableQueryColumnVO uiTableQueryColumnVO : queryColumnVOS) {
            IntrospectedColumn introspectedColumn = introspectedTable.getColumn(uiTableQueryColumnVO.getColumnName());

            if (RootClassInfo.getInstance(rootClass, warnings).containsProperty(introspectedColumn) || introspectedColumn == null) {
                continue;
            }

            if (uiTableQueryColumnVO.isRuzzyQuery()) {
                IntrospectedColumn searchIntrospectedColumn = new IntrospectedColumn();
                try {
                    BeanUtils.copyProperties(searchIntrospectedColumn, introspectedColumn);

                    searchIntrospectedColumn.setJavaProperty("search" + firstCharacterUppercase(searchIntrospectedColumn.getJavaProperty(), true));
                    addLambda("like", searchIntrospectedColumn, introspectedColumn, flag);
                    Field field = addField(searchIntrospectedColumn, topLevelClass);
                    addApiModelProperty(flag, topLevelClass, field, "模糊查询" + introspectedColumn.getRemarks(), false);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    warnings.add(e.getMessage());
                }
            }

            if (uiTableQueryColumnVO.isRangeQuery()) {
                IntrospectedColumn startIntrospectedColumn = new IntrospectedColumn();
                IntrospectedColumn endIntrospectedColumn = new IntrospectedColumn();

                try {
                    BeanUtils.copyProperties(startIntrospectedColumn, introspectedColumn);
                    BeanUtils.copyProperties(endIntrospectedColumn, introspectedColumn);

                    startIntrospectedColumn.setJavaProperty("start" + firstCharacterUppercase(startIntrospectedColumn.getJavaProperty(), true));
                    endIntrospectedColumn.setJavaProperty("end" + firstCharacterUppercase(endIntrospectedColumn.getJavaProperty(), true));
                    addApiModelProperty(flag, topLevelClass, addField(startIntrospectedColumn, topLevelClass), introspectedColumn.getRemarks() + "开始", false);
                    addApiModelProperty(flag, topLevelClass, addField(endIntrospectedColumn, topLevelClass), introspectedColumn.getRemarks() + "结束", false);
                    addLambda("ge", startIntrospectedColumn, introspectedColumn, flag);
                    addLambda("le", endIntrospectedColumn, introspectedColumn, flag);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            if (uiTableQueryColumnVO.isOrderBy()) {
                IntrospectedColumn orderByIntrospectedColumn = new IntrospectedColumn();

                try {
                    BeanUtils.copyProperties(orderByIntrospectedColumn, introspectedColumn);

                    orderByIntrospectedColumn.setFullyQualifiedJavaType(new FullyQualifiedJavaType("java.lang.Boolean"));
                    orderByIntrospectedColumn.setJavaProperty("orderBy" + firstCharacterUppercase(orderByIntrospectedColumn.getJavaProperty(), true));
                    Field field = addField(orderByIntrospectedColumn, topLevelClass);
                    addApiModelProperty(flag, topLevelClass, field, "根据" + introspectedColumn.getRemarks() + "排序", false);
                    addLambdaOrder(orderByIntrospectedColumn, introspectedColumn, flag);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }

            if (uiTableQueryColumnVO.isChecked()) {
                addApiModelProperty(flag, topLevelClass, addField(introspectedColumn, topLevelClass), introspectedColumn.getRemarks(), false);
                addLambda("eq", introspectedColumn, introspectedColumn, flag);
            }
        }


        return topLevelClass;
    }

    private void addLambda(String function, IntrospectedColumn searchIntrospectedColumn, IntrospectedColumn introspectedColumn, boolean flag) {
        if (!flag) return;
        String queryName = "get" + firstCharacterUppercase(searchIntrospectedColumn.getJavaProperty(), true);
        String beanName = "get" + firstCharacterUppercase(introspectedColumn.getJavaProperty(), true);
        List<String> list = query.computeIfAbsent(introspectedColumn.getJavaProperty(), key -> new ArrayList<>());

        if (searchIntrospectedColumn.getFullyQualifiedJavaType().getFullyQualifiedName().startsWith("java.lang.String")) {
            list.add(String.format("\t\t\t\n.%s(StringUtils.isNotBlank(query.%s()), %s::%s, query.%s())", function, queryName, getName("DO"), beanName, queryName));
        } else {
            list.add(String.format("\t\t\t\n.%s(Objects.nonNull(query.%s()), %s::%s, query.%s())", function, queryName, getName("DO"), beanName, queryName));
        }
    }

    private void addLambdaOrder(IntrospectedColumn searchIntrospectedColumn, IntrospectedColumn introspectedColumn, boolean flag) {
        if (!flag) return;
        String queryName = "get" + firstCharacterUppercase(searchIntrospectedColumn.getJavaProperty(), true);
        String beanName = "get" + firstCharacterUppercase(introspectedColumn.getJavaProperty(), true);

        List<String> list = query.computeIfAbsent(introspectedColumn.getJavaProperty(), key -> new ArrayList<>());
        list.add(String.format("\t\t\t\n.orderBy(Objects.nonNull(query.%s()), query.%s() != null?query.%s():false, %s::%s)", queryName, queryName, queryName, getName("DO"), beanName));
    }

    private void addApiModelProperty(boolean flag, TopLevelClass topLevelClass, Field field, String value, boolean required) {
        if (!flag) return;
        addApiModelProperty(field, value, required);
        addDateFormat(flag, topLevelClass, field);
    }

    private void addDateFormat(boolean flag, TopLevelClass topLevelClass, Field field) {
        if (!flag) return;

        if (field.getType().getFullyQualifiedName().equals("java.util.Date") || field.getType().getFullyQualifiedName().equals("java.sql.Date")) {
            topLevelClass.addImportedType("org.springframework.format.annotation.DateTimeFormat");
            topLevelClass.addImportedType("com.fasterxml.jackson.annotation.JsonFormat");
            field.addAnnotation("@DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")");
            field.addAnnotation("@JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", locale = \"zh\", timezone = \"Asia/Shanghai\")");
        } else if (field.getType().getFullyQualifiedName().equals(LocalDateTime.class.getName())) {
            topLevelClass.addImportedType("org.springframework.format.annotation.DateTimeFormat");
            field.addAnnotation("@DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")");
        } else if (field.getType().getFullyQualifiedName().equals(LocalDate.class.getName())) {
            topLevelClass.addImportedType("org.springframework.format.annotation.DateTimeFormat");
            field.addAnnotation("@DateTimeFormat(pattern = \"yyyy-MM-dd\")");
        } else if (field.getType().getFullyQualifiedName().equals(LocalTime.class.getName())) {
            topLevelClass.addImportedType("org.springframework.format.annotation.DateTimeFormat");
            field.addAnnotation("@DateTimeFormat(pattern = \"HH:mm:ss\")");
        }
    }

    private void addApiModelProperty(Field field, String value, boolean required) {
        value = makeQueryStringAllRegExp(value);

        if (required) {
            field.addAnnotation(String.format("@ApiModelProperty(value = \"%s\", required = true)", value));
        } else {

            field.addAnnotation(String.format("@ApiModelProperty(value = \"%s\")", value));
        }
    }

    private Element include(String name) {
        XmlElement xmlElement = new XmlElement("include");
        xmlElement.addAttribute(new Attribute("refid", name));
        return xmlElement;
    }

    private void addSqlIf(IntrospectedColumn introspectedColumn, XmlElement element, String text) {
        addSqlIf(introspectedColumn, element, introspectedColumn.getFullyQualifiedJavaType().getFullyQualifiedNameWithoutTypeParameters().equals(String.class.getName()) ? "%s != null and %s != ''" : "%s != null", text);
    }

    private void addSqlIf(IntrospectedColumn introspectedColumn, XmlElement element, String test, String text) {
        XmlElement xmlElement = new XmlElement("if");
        xmlElement.addAttribute(new Attribute("test", String.format(test, introspectedColumn.getJavaProperty(), introspectedColumn.getJavaProperty())));
        xmlElement.addElement(new TextElement(text));
        element.addElement(xmlElement);
    }

    private Field addField(IntrospectedColumn introspectedColumn, TopLevelClass topLevelClass) {
        Plugin plugins = context.getPlugins();

        Field field = getJavaBeansField(introspectedColumn, context, introspectedTable);
        if (plugins.modelFieldGenerated(field, topLevelClass,
                introspectedColumn, introspectedTable,
                Plugin.ModelClassType.BASE_RECORD)) {
            topLevelClass.addField(field);
            topLevelClass.addImportedType(field.getType());
        }

        Method method = getJavaBeansGetter(introspectedColumn, context, introspectedTable);
        if (plugins.modelGetterMethodGenerated(method, topLevelClass,
                introspectedColumn, introspectedTable,
                Plugin.ModelClassType.BASE_RECORD)) {
            topLevelClass.addMethod(method);
        }

        if (!introspectedTable.isImmutable()) {
            method = getJavaBeansSetter(introspectedColumn, context, introspectedTable);
            if (plugins.modelSetterMethodGenerated(method, topLevelClass,
                    introspectedColumn, introspectedTable,
                    Plugin.ModelClassType.BASE_RECORD)) {
                topLevelClass.addMethod(method);
            }
        }

        return field;
    }

    private void addTopLevelClass(TopLevelClass topLevelClass, List<CompilationUnit> compilationUnits) {
        if (context.getPlugins().modelBaseRecordClassGenerated(
                topLevelClass, introspectedTable)) {
            compilationUnits.add(topLevelClass);
        }
    }

    private FullyQualifiedJavaType getSuperClass() {
        FullyQualifiedJavaType superClass;
        if (introspectedTable.getRules().generatePrimaryKeyClass()) {
            superClass = new FullyQualifiedJavaType(introspectedTable
                    .getPrimaryKeyType());
        } else {
            String rootClass = getRootClass();
            if (rootClass != null) {
                superClass = new FullyQualifiedJavaType(rootClass);
            } else {
                superClass = null;
            }
        }

        return superClass;
    }

    private boolean includePrimaryKeyColumns() {
        return !introspectedTable.getRules().generatePrimaryKeyClass()
                && introspectedTable.hasPrimaryKeyColumns();
    }

    private boolean includeBLOBColumns() {
        return !introspectedTable.getRules().generateRecordWithBLOBsClass()
                && introspectedTable.hasBLOBColumns();
    }

    private void addParameterizedConstructor(TopLevelClass topLevelClass, List<IntrospectedColumn> constructorColumns) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setConstructor(true);
        method.setName(topLevelClass.getType().getShortName());
        context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);

        for (IntrospectedColumn introspectedColumn : constructorColumns) {
            method.addParameter(new Parameter(introspectedColumn.getFullyQualifiedJavaType(),
                    introspectedColumn.getJavaProperty()));
            topLevelClass.addImportedType(introspectedColumn.getFullyQualifiedJavaType());
        }

        StringBuilder sb = new StringBuilder();
        List<String> superColumns = new LinkedList<String>();
        if (introspectedTable.getRules().generatePrimaryKeyClass()) {
            boolean comma = false;
            sb.append("super("); //$NON-NLS-1$
            for (IntrospectedColumn introspectedColumn : introspectedTable.getPrimaryKeyColumns()) {
                if (comma) {
                    sb.append(", "); //$NON-NLS-1$
                } else {
                    comma = true;
                }
                sb.append(introspectedColumn.getJavaProperty());
                superColumns.add(introspectedColumn.getActualColumnName());
            }
            sb.append(");"); //$NON-NLS-1$
            method.addBodyLine(sb.toString());
        }

        for (IntrospectedColumn introspectedColumn : constructorColumns) {
            if (!superColumns.contains(introspectedColumn.getActualColumnName())) {
                sb.setLength(0);
                sb.append("this."); //$NON-NLS-1$
                sb.append(introspectedColumn.getJavaProperty());
                sb.append(" = "); //$NON-NLS-1$
                sb.append(introspectedColumn.getJavaProperty());
                sb.append(';');
                method.addBodyLine(sb.toString());
            }
        }

        topLevelClass.addMethod(method);
    }

    private List<IntrospectedColumn> getColumnsInThisClass() {
        List<IntrospectedColumn> introspectedColumns;
        if (includePrimaryKeyColumns()) {
            if (includeBLOBColumns()) {
                introspectedColumns = introspectedTable.getAllColumns();
            } else {
                introspectedColumns = introspectedTable.getNonBLOBColumns();
            }
        } else {
            if (includeBLOBColumns()) {
                introspectedColumns = introspectedTable
                        .getNonPrimaryKeyColumns();
            } else {
                introspectedColumns = introspectedTable.getBaseColumns();
            }
        }

        return introspectedColumns;
    }

    public void setQueryColumnVOS(List<UITableQueryColumnVO> queryColumnVOS) {
        this.queryColumnVOS = queryColumnVOS;
    }

    public void setClientPackage(String clientPackage) {
        this.clientPackage = clientPackage;
    }

    public void setModelPackage(String modelPackage) {
        this.modelPackage = modelPackage;
    }

    public void setServicePackage(String servicePackage) {
        this.servicePackage = servicePackage;
    }

    public void setControllerPackage(String controllerPackage) {
        this.controllerPackage = controllerPackage;
    }

    public void setDocument(Document document) {
        this.document = document;
    }

    public boolean isMybatisPlus() {
        return isMybatisPlus;
    }

    public void setMybatisPlus(boolean mybatisPlus) {
        isMybatisPlus = mybatisPlus;
    }

    public static String bigLetterToSmallAndBeforeAdd_(String name) {
        StringBuffer stringBuffer = new StringBuffer();
        char[] chars = name.toCharArray();
        for (char ch : chars) {
            boolean digit = Character.isUpperCase(ch);
            if (digit) {
                String str = String.valueOf(ch);
                String lstr = str.toLowerCase();
                stringBuffer.append("/");
                stringBuffer.append(lstr);
            } else {
                stringBuffer.append(ch);
            }
        }
        return stringBuffer.toString();
    }
}
